/**
 * 游戏房间管理模块
 * 设计游戏房间，供用户匹配成功后建立游戏房间
 * 游戏房间要管理房间中的数据(房间ID，房间中的用户ID，棋盘信息等)，同时要对房间中的动作做处理(用户之间的聊天、下棋)
 * 设计房间管理类，对建立的游戏房间进行管理(是创建、查找、销毁房间等)
 */
#pragma once
#include"DataBase.hpp"
#include"OnlineManager.hpp"

#define ROW 15
#define COL 15

namespace OnlineGoBang
{
    //房间状态
    typedef enum{GAME_START,GAME_OVER}room_statu;
    //棋子颜色
    typedef enum{WHITE=1,BLACK}chess_color;

    using UserTablePtr=std::shared_ptr<UserTable>;
    using OnlineManagerPtr=std::shared_ptr<OnlineManager>;

    //游戏房间类
    class Room
    {
        //以(row，col)为基点，判断是否构成五颗及以上棋子连成线
        bool CheckFive(int row,int col,int row_off,int col_off,int color)
        {
            //(row,col)为基点，row_off、col_off为相对于基点的偏移量
            int count=1;
            int cur_row=row+row_off;
            int cur_col=col+col_off;
            while(cur_row>=0&&cur_row<ROW&&cur_col>=0&&cur_col<COL)
            {
                if(_board[cur_row][cur_col]==color)
                {
                    count++;
                }
                cur_row+=row_off;
                cur_col+=col_off;
            }
            //继续往反向遍历
            cur_row=row-row_off;
            cur_col=col-col_off;
            while(cur_row>=0&&cur_row<ROW&&cur_col>=0&&cur_col<COL)
            {
                if(_board[cur_row][cur_col]==color)
                {
                    count++;
                }
                cur_row-=row_off;
                cur_col-=col_off;
            }
            return count>=5?true:false;
        }
        //检查是否胜利，返回胜者ID，否则返回0
        uint64_t CheckWin(int row,int col,int color)
        {
            if(CheckFive(row,col,0,1,color)
            ||CheckFive(row,col,1,0,color)
            ||CheckFive(row,col,1,1,color)
            ||CheckFive(row,col,1,-1,color))
            {
                return color==WHITE?_white_id:_black_id;
            }
            return 0;
        }

        //设置响应
        void SetResponse(Json::Value& resp,const std::string& optype,bool result,const std::string& reason
                        ,uint64_t room_id,uint64_t cur_uid,int row,int col,uint64_t winner_id)
        {
            resp["optype"]=optype;
            resp["result"]=result;
            resp["reason"]=reason;
            resp["room_id"]=(Json::UInt64)room_id;
            resp["uid"]=(Json::UInt64)cur_uid;
            resp["row"]=row;
            resp["col"]=col;
            resp["winner"]=(Json::UInt64)winner_id;
        }

    public:

        Room(const uint64_t& room_id,UserTable* ut,OnlineManager* om)
            :_room_id(room_id)
            ,_statu(GAME_START)
            ,_player_count(0)
            ,_white_id(0)
            ,_black_id(0)
            ,_user_table(ut)
            ,_online_user(om)
            ,_board(ROW,std::vector<int>(COL,0))
        {
            LOG(INFO,"%lu 房间创建成功\n",_room_id);
        }
        Room(const uint64_t& room_id,UserTablePtr& ut,const OnlineManagerPtr& om)
            :_room_id(room_id)
            ,_statu(GAME_START)
            ,_player_count(0)
            ,_white_id(0)
            ,_black_id(0)
            ,_user_table(ut)
            ,_online_user(om)
            ,_board(ROW,std::vector<int>(COL,0))
        {
            LOG(INFO,"%lu 房间创建成功\n",_room_id);
        }
        ~Room()
        {
            LOG(INFO,"%lu 房间销毁\n",_room_id);
        }

        //获取房间id
        uint64_t RoomID()
        {
            return _room_id;
        }
        //获取房间状态
        room_statu RoomStatu()
        {
            return _statu;
        }
        //获取房间中玩家数量
        int PlayerCount()
        {
            return _player_count;
        }

        //设置白棋玩家
        void SetWhiteUser(const uint64_t& uid)
        {
            _white_id=uid;
            _player_count++;
        }
        //设置黑棋玩家
        void SetBlackUser(const uint64_t& uid)
        {
            _black_id=uid;
            _player_count++;
        }
        //获取白棋玩家ID
        uint64_t GetWhiteUser()
        {
            return _white_id;
        }
        //获取黑棋玩家ID
        uint64_t GetBlackUser()
        {
            return _black_id;
        }

        //处理下棋动作
        Json::Value HandleChess(const Json::Value& req)
        {
            Json::Value resp;
            if(_statu==GAME_OVER)
            {
                SetResponse(resp,"put_chess",false,"当前对局已结束！",_room_id,0,-1,-1,0);
                return resp;
            }
            //判断房间中的玩家是否在线，任意一个不在线，另一个就胜利
            int row=req["row"].asInt();
            int col=req["col"].asInt();
            uint64_t cur_uid=req["uid"].asUInt64();//当前请求的用户ID
            if(!_online_user->InRoom(_white_id))//白棋玩家掉线
            {
                // resp["optype"]="put_chess";
                // resp["result"]=true;
                // resp["reason"]="对方掉线!我方胜利";
                // resp["room_id"]=(Json::UInt64)_room_id;
                // resp["uid"]=(Json::UInt64)cur_uid;
                // resp["row"]=row;
                // resp["col"]=col;
                // resp["winner"]=(Json::UInt64)_black_id;
                SetResponse(resp,"put_chess",true,"白棋掉线！黑棋胜利！",_room_id,cur_uid,row,col,_black_id);

                return resp;
            }
            if(!_online_user->InRoom(_black_id))//黑棋玩家掉线
            {
                // resp["optype"]="put_chess";
                // resp["result"]=true;
                // resp["reason"]="对方掉线!我方胜利";
                // resp["room_id"]=(Json::UInt64)_room_id;
                // resp["uid"]=(Json::UInt64)cur_uid;
                // resp["row"]=row;
                // resp["col"]=col;
                // resp["winner"]=(Json::UInt64)_white_id;

                SetResponse(resp,"put_chess",true,"黑棋掉线！白棋胜利！",_room_id,cur_uid,row,col,_white_id);
                return resp;
            }

            //判断走棋位置是否合理
            if(_board[row][col]!=0)
            {
                resp["optype"]="put_chess";
                resp["result"]=false;
                resp["reason"]="当前位置已经有棋子了!";
                return resp;
            }
            int cur_color=cur_uid==_white_id?WHITE:BLACK;
            _board[row][col]=cur_color;

            //检查是否有胜利的
            uint64_t winner_id=CheckWin(row,col,cur_color);
            
            // resp["optype"]="put_chess";
            // resp["result"]=true;
            // resp["reason"]="五星连珠!我方胜利";
            // resp["room_id"]=(Json::UInt64)_room_id;
            // resp["uid"]=(Json::UInt64)cur_uid;
            // resp["row"]=row;
            // resp["col"]=col;
            // resp["winner"]=(Json::UInt64)winner_id;
            if(winner_id==_white_id)
            {
                SetResponse(resp,"put_chess",true,"五星连珠!白棋胜利",_room_id,cur_uid,row,col,winner_id);
                _statu=GAME_OVER;
            }
            else if(winner_id==_black_id)
            {
                SetResponse(resp,"put_chess",true,"五星连珠!黑棋胜利",_room_id,cur_uid,row,col,winner_id);
                _statu=GAME_OVER;
            }
            else
            {
                SetResponse(resp,"put_chess",true,"走棋",_room_id,cur_uid,row,col,winner_id);
            }
            return resp;
        }

        //处理聊天动作
        Json::Value HandleChat(const Json::Value& req)
        {
            Json::Value resp=req;

            //检查消息中是否有敏感词
            std::string msg=req["message"].asString();
            int pos=msg.find("垃圾");
            if(pos!=std::string::npos)
            {
                resp["result"]=false;
                resp["reason"]="消息中包含敏感词，不能发送!";
                return resp;
            }

            resp["result"]=true;
            return resp;
        }

        //处理玩退出动作
        void HandleExit(const uint64_t& uid)
        {
            Json::Value resp;
            if(_statu==GAME_START)
            {
                uint64_t winner_id=uid==_white_id?_black_id:_white_id;
                if(winner_id==_white_id)
                {
                    SetResponse(resp,"put_chess",true,"黑棋退出!白棋胜利",_room_id,uid,-1,-1,_white_id);
                }
                else if(winner_id==_black_id)
                {
                    SetResponse(resp,"put_chess",true,"白棋退出!黑棋胜利",_room_id,uid,-1,-1,_black_id);
                }
                uint64_t loser_id=winner_id==_white_id?_black_id:_white_id;
                _user_table->Win(winner_id);
                _user_table->Lose(loser_id);
                _statu=GAME_OVER;

                Broadcast(resp);
            }
            _player_count--;
        }

        //处理请求，根据请求类型调用对应的函数，得到响应后再房间中进行广播
        void HandleRequest(const Json::Value& req)
        {
            Json::Value resp;
            //判断请求的房间ID与当前房间ID是否匹配
            uint64_t room_id=req["room_id"].asUInt64();
            if(room_id!=_room_id)
            {
                resp["optype"]=req["optype"].asString();
                resp["result"]=false;
                resp["reason"]="房间号不匹配!";
                Broadcast(resp);
                return ;
            }

            //根据请求类型调用相关函数
            std::string optype=req["optype"].asString();
            if(optype=="put_chess")
            {
                resp=HandleChess(req);
                uint64_t winner_id=resp["winner"].asUInt64();
                if(winner_id!=0)
                {
                    //更新数据库信息
                    uint64_t loser_id=winner_id==_white_id?_black_id:_white_id;
                    _user_table->Win(winner_id);
                    _user_table->Lose(loser_id);

                    _statu=GAME_OVER;   
                }
            }
            else if(optype=="chat")
            {
                resp=HandleChat(req);
            }
            else
            {
                resp["optype"]=req["optype"].asString();
                resp["result"]=false;
                resp["reason"]="未知的请求!";
            }

            Broadcast(resp);
        }
        
        //将指定信息广播给房间中的玩家
        void Broadcast(const Json::Value& msg)
        {
            //处理数据，将msg中的Json::Value格式的数据进行序列化处理，生成Json格式的字符串
            std::string body;
            Detail::JSON::Serialize(msg,body);
            LOG(DEBUG,"广播消息：%s\n",body.c_str());

            wsserver_t::connection_ptr wcon=_online_user->GetConnectionFromRoom(_white_id);
            if(wcon.get()==nullptr)
            {
                LOG(ERROR,"获取白棋玩家连接失败\n");
            }
            else
            {
                wcon->send(body);
            }

            wsserver_t::connection_ptr bcon=_online_user->GetConnectionFromRoom(_black_id);
            if(bcon.get()==nullptr)
            {
                LOG(ERROR,"获取黑棋玩家连接失败\n");
            }
            else
            {
                bcon->send(body);
            }
        }
        
    private:
        uint64_t _room_id;//房间ID
        room_statu _statu;//房间状态
        int _player_count;//该房间玩家数量
        uint64_t _white_id;//执白子玩家ID
        uint64_t _black_id;//执黑子玩家ID
        UserTablePtr _user_table;//用户数据表
        OnlineManagerPtr _online_user;//在线用户
        std::vector<std::vector<int>> _board;//棋盘
    };

    using RoomPtr=std::shared_ptr<Room>;

    //游戏房间管理类
    class RoomManager
    {
    public:
        using RoomManagerPtr=std::shared_ptr<RoomManager>;
        RoomManager(UserTable* ut,OnlineManager* om)
            :_next_rid(1),_user_table(ut),_online_user(om)
        {}
        RoomManager(UserTablePtr& ut,OnlineManagerPtr& om)
            :_next_rid(1),_user_table(ut),_online_user(om)
        {}
        ~RoomManager()
        {}

        //创建房间
        RoomPtr CreateRoom(uint64_t uid1,uint64_t uid2)
        {
            //判断两个用户是否都在线
            if(!_online_user->InHall(uid1))
            {
                LOG(ERROR,"用户%lu不在大厅中,创建房间失败\n",uid1);
                return RoomPtr();
            }
            if(!_online_user->InHall(uid2))
            {
                LOG(ERROR,"用户%lu不在大厅中,创建房间失败\n",uid2);
                return RoomPtr();
            }

            //创建房间
            std::unique_lock<std::mutex> lock(_mutex);
            RoomPtr rp=std::make_shared<Room>(_next_rid,_user_table,_online_user);
            rp->SetWhiteUser(uid1);
            rp->SetBlackUser(uid2);

            //管理房间
            _rooms.insert({_next_rid,rp});
            _users.insert({uid1,_next_rid});
            _users.insert({uid2,_next_rid});

            _next_rid++;
            return rp;
        }

        //通过房间ID获取房间信息
        RoomPtr GetRoomByRid(uint64_t rid)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it=_rooms.find(rid);
            if(it==_rooms.end())
            {
                return RoomPtr();
            }
            return it->second;
        }

        //通过用户ID获取房间信息
        RoomPtr GetRoomByUid(uint64_t uid)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            //通过用户ID找房间ID，在通过房间ID找房间
            auto it=_users.find(uid);
            if(it==_users.end())
            {
                return RoomPtr();
            }
            auto rt=_rooms.find(it->second);
            if(rt==_rooms.end())
            {
                return RoomPtr();
            }
            return rt->second;
        }

        //销毁房间
        void RemoveRoom(uint64_t rid)
        {
            //获取房间信息
            RoomPtr rp=GetRoomByRid(rid);
            if(rp.get()==nullptr)
            {
                _rooms.erase(rid); 
                return;
            }

            //获取房间中用户ID
            uint64_t uid1=rp->GetWhiteUser();
            uint64_t uid2=rp->GetBlackUser();

            std::unique_lock<std::mutex> lock(_mutex);
            //删除用户信息
            _users.erase(uid1);
            _users.erase(uid2);
            //删除房间信息
            _rooms.erase(rid);
        }

        //删除房间中指定用户
        void RemoveUser(uint64_t uid)
        {
            RoomPtr rp=GetRoomByUid(uid);
            if(rp.get()==nullptr)
            {
                return;
            }
            //uid用户退出
            rp->HandleExit(uid);
            if(rp->PlayerCount()==0)//房间中没有用户了
            {
                RemoveRoom(rp->RoomID());
            }
            return;
        }

    private:
        uint64_t _next_rid;//房间ID分配计数器
        std::mutex _mutex;
        UserTablePtr _user_table;//用户数据表
        OnlineManagerPtr _online_user;//在线用户
        std::unordered_map<uint64_t,RoomPtr> _rooms;//房间ID与房间的映射关系
        std::unordered_map<uint64_t,uint64_t> _users;//用户ID与房间ID的映射关系，通过通ID找房间ID，再通过房间ID找对应的房间

    };
}